package cn.com.bluemoon.daps.standard.service.impl;

import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.com.bluemoon.daps.api.model.RemoteDataModelRecordService;
import cn.com.bluemoon.daps.api.model.RemoteDataModelService;
import cn.com.bluemoon.daps.api.model.RemoteDataModelTableService;
import cn.com.bluemoon.daps.api.sys.RemoteSystemDatasourceService;
import cn.com.bluemoon.daps.api.sys.RemoteSystemInfoService;
import cn.com.bluemoon.daps.common.constant.DapConstant;
import cn.com.bluemoon.daps.common.domain.ResultBean;
import cn.com.bluemoon.daps.common.enums.AuditCheckType;
import cn.com.bluemoon.daps.common.enums.AuditStatus;
import cn.com.bluemoon.daps.common.enums.BmStatus;
import cn.com.bluemoon.daps.common.exception.DapException;
import cn.com.bluemoon.daps.common.toolkit.BmAssetUtils;
import cn.com.bluemoon.daps.model.dto.DapDataModelTableDto;
import cn.com.bluemoon.daps.model.dto.ModelDto;
import cn.com.bluemoon.daps.model.dto.ModelTableDto;
import cn.com.bluemoon.daps.model.entity.DapDataModel;
import cn.com.bluemoon.daps.standard.common.report.IReportCheck;
import cn.com.bluemoon.daps.standard.dto.AuditFieldStdNoRelationResult;
import cn.com.bluemoon.daps.standard.entity.*;
import cn.com.bluemoon.daps.standard.mapper.DapAuditChinResultMapper;
import cn.com.bluemoon.daps.standard.mapper.DapAuditStandardResultMapper;
import cn.com.bluemoon.daps.standard.service.*;
import cn.com.bluemoon.daps.standard.vo.SysAuditReportFieldSearchVo;
import cn.com.bluemoon.daps.standard.vo.SysAuditReportSearchVo;
import cn.com.bluemoon.daps.standard.vo.SystemAuditSearchVo;
import cn.com.bluemoon.daps.system.dto.SystemInfoDto;
import cn.com.bluemoon.daps.system.entity.DapSystemDatasource;
import cn.com.bluemoon.daps.system.entity.DapSystemInfo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.poi.ss.usermodel.Workbook;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;

import static java.util.stream.Collectors.toList;

/**
 * 系统稽核服务
 *
 * @author Jarod.Kong
 * @date 2020/9/2 14:40
 */
@Slf4j
@Service
@Transactional(rollbackFor = {DapException.class, Exception.class})
public class DapSystemAuditServiceImpl implements DapSystemAuditService {

    public static final String EXPORT_CHI_RESULT_KEY = "系统稽核-表中文名检查.xls";
    public static final String EXPORT_CHI_FIELD_RESULT_KEY = "系统稽核-字段中文名缺少列表.xls";
    public static final String EXPORT_STD_RESULT_KEY = "系统稽核-标准稽核列表.xls";
    public static final String EXPORT_STD_FIELD_RESULT_KEY = "系统稽核-未关联标准列表.xls";
    @Resource
    private RemoteSystemInfoService systemInfoService;
    @Resource
    private RemoteDataModelService dataModelService;
    @Resource
    private RemoteSystemDatasourceService datasourceService;
    @Resource
    private RemoteDataModelTableService modelTableService;

    @Resource(name = DapConstant.AUDIT_SYSTEM_REPORT_STANDARD)
    private IReportCheck reportStandardCheck;
    @Resource(name = DapConstant.AUDIT_SYSTEM_REPORT_CHINESE)
    private IReportCheck reportChineseCheck;
    @Resource(name = DapConstant.AUDIT_SYSTEM_REPORT_MAIN)
    private IReportCheck reportMainCheck;
    @Resource
    private DapAuditSystemResultService auditSystemResultService;
    @Resource
    private RemoteDataModelRecordService modelRecordService;
    @Resource
    private DapAuditChinResultService auditChinResultService;
    @Resource
    private DapAuditFieldChinResultService auditFieldChinResultService;
    @Resource
    private DapAuditStandardResultService auditStandardResultService;
    @Resource
    private DapAuditFieldStandardResultService auditFieldStandardResultService;

    @Override
    public IPage<SystemInfoDto> findSysAuditListPage(SystemAuditSearchVo auditSearchVo) {
        IPage<SystemInfoDto> page = systemInfoService.getSysInfoPage(auditSearchVo.getCurrent(),
                auditSearchVo.getPageSize(),
                auditSearchVo.getKeyword(), BmStatus.ON);
        // 根据系统id获取相关模型中，最新发布的时间
        return page.convert(new Function<SystemInfoDto, SystemInfoDto>() {
            @Override
            public SystemInfoDto apply(SystemInfoDto s) {
                DapSystemInfo sysInfo = s.getDapSystemInfo();
                String sysId = sysInfo.getId();
                Optional<LocalDateTime> maxOpt = modelRecordService.findMaxReleaseTimeBySystemId(sysId);
                sysInfo.setModelTopNewestPublishTime(maxOpt.orElse(null));
                return s;
            }
        });
    }

    @Override
    public List<DapAuditSystemResult> findAuditSystemResultList(String sysAuditId) {
        LambdaQueryWrapper<DapAuditSystemResult> where = auditSystemResultService.bmLambdaQuery().eq(DapAuditSystemResult::getAuditResultId, sysAuditId);
        return auditSystemResultService.list(where);
    }

    @Override
    public List<DapDataModel> findModelInfosBySystemId(String sysId) {
        return findModelsBySystemId(sysId);
    }

    @Override
    public IPage<DapAuditChinResult> findAuditSystemAuditChinResults(SysAuditReportSearchVo<DapAuditChinResult> searchV) {
        //noinspection unchecked
        LambdaQueryWrapper<DapAuditChinResult> eq = auditChinResultService.bmLambdaQuery()
                .and(StringUtils.isNotBlank(searchV.getKeyword()), (wrapper) -> {
                    return wrapper
                            .like(StringUtils.isNotBlank(searchV.getKeyword()), DapAuditChinResult::getTableName, searchV.getKeyword())
                            .or()
                            .like(StringUtils.isNotBlank(searchV.getKeyword()), DapAuditChinResult::getTableChinName, searchV.getKeyword());
                })
                .eq(DapAuditChinResult::getSystemAuditResultId, searchV.getSysAuditId())
                .eq(StringUtils.isNotBlank(searchV.getModelId()), DapAuditChinResult::getModelId, searchV.getModelId())
                .orderByAsc(DapAuditChinResult::getIsPassed, DapAuditChinResult::getModelName, DapAuditChinResult::getTableName);
        return auditChinResultService.page(searchV.toMpPage(), eq);
    }

    @Override
    public IPage<DapAuditFieldChinResult> findAuditFieldChinResults(SysAuditReportFieldSearchVo<DapAuditFieldChinResult> searchV) {
        LambdaQueryWrapper<DapAuditFieldChinResult> eq = auditFieldChinResultService.bmLambdaQuery()
                .and(StringUtils.isNotBlank(searchV.getKeyword()), (wrapper) -> {
                    return wrapper
                            .like(StringUtils.isNotBlank(searchV.getKeyword()), DapAuditFieldChinResult::getFieldName, searchV.getKeyword())
                            .or()
                            .like(StringUtils.isNotBlank(searchV.getKeyword()), DapAuditFieldChinResult::getFieldChinName, searchV.getKeyword());
                })
                .eq(DapAuditFieldChinResult::getChinResultId, searchV.getAuditTableResultId())
                .orderByAsc(DapAuditFieldChinResult::getIsPassed);
        return auditFieldChinResultService.page(searchV.toMpPage(), eq);
    }

    @Override
    public void startAuditBySysId(String sysId) throws DapException {
        DapSystemInfo sysInfo = systemInfoService.getById(sysId).orElseThrow(()-> new DapException("获取系统信息已被删除"));
        generateReportBySystemId(sysInfo);
    }

    /**
     * 生成报告 1)中文检查报告(table/field) 2)标准检查（table/field)
     * 注意：模型的基础数据，必须是已发布的，若初始化模型没走流程，则不可进行稽核
     * 1.抽取公共逻辑（大类公共部分：获取系统信息、获取对应模型信息、获取模型的所有表和字段（已发布的，记录当前模型的<em>发布版本时间</em>）、
     * 数据量评估，一个系统对应多个模型，每个模型对应多个表信息和表字段信息
     * 假设：
     * 1 system => 2 model
     * 1 model => 2000 table * 每个表字段数（每行列数） 10 (考虑减少到必须的字段，大概2-4个）
     * 1 table => 100 field  * 每个字段的字段数（每行列数） 10 (考虑减少到必须的字段，大概2-4个）
     * 一次获取到的数据量 => 2* 2000 * 4 * 100 * 4 = 4000,0000(千万级) => 10亿大概1G
     * <p>
     * 2.针对各自特征具体实现
     * 实现：
     * 1.获取基础信息，从模型字段出发，统计结果作为模型表报告内容
     * 1.0 获取公共资源
     * 1.1 启动两个线程并行，跑中文检查、标准检查
     * 1.2 对每个模型进行并行处理
     *
     * @param sysInfo 系统id不可为空
     */
    @Override
    public void generateReportBySystemId(DapSystemInfo sysInfo) throws DapException {
        final String systemId = sysInfo.getId();
        final String sysAuditMainResultId = IdWorker.getIdStr();
        final String sysAuditChineseResultId = IdWorker.getIdStr();
        boolean insertChi = reportMainCheck.generateMainReport(sysAuditMainResultId, systemId, sysAuditChineseResultId, AuditCheckType.CHINESE);
        BmAssetUtils.isTrue(insertChi, "写入[" + AuditCheckType.CHINESE.getCheckType() + "]失败");
        String sysAuditStandardResultId = IdWorker.getIdStr();
        boolean insertStd = reportMainCheck.generateMainReport(sysAuditMainResultId, systemId, sysAuditStandardResultId, AuditCheckType.STANDARD);
        BmAssetUtils.isTrue(insertStd, "写入[" + AuditCheckType.STANDARD.getCheckType() + "]失败");
        // 报告生成
        long start = System.currentTimeMillis();
        List<ModelDto> dapDataModels = findModelAndTableAndFieldsBySystemId(systemId);
        List<Boolean> checkChiIsPass = new CopyOnWriteArrayList<>();
        List<Boolean> checkStdIsPass = new CopyOnWriteArrayList<>();
        dapDataModels.parallelStream()
                .filter(m -> !m.getTableModelDtos().isEmpty())
                .forEach(modelDto -> {
                    String modelName = modelDto.getModel().getName();
                    modelDto.setSystemId(systemId);
                    // 生成中文检查
                    final ModelDto toModelDto = copyModelDto(sysAuditChineseResultId, modelDto);
                    CompletableFuture<Void> checkChi = CompletableFuture.supplyAsync(() -> {
                        long s1 = System.currentTimeMillis();
                        boolean isPass = reportChineseCheck.generate(toModelDto);
                        long s2 = System.currentTimeMillis();
                        log.info("模型[{}]的中文检查完成，是否通过情况：{}, cost:{}s", modelName, isPass, (s2 - s1) / 1000);
                        return isPass;
                    }).thenAccept(checkChiIsPass::add);
                    // 生成标准检查
                    final ModelDto toModelDtoStd = copyModelDto(sysAuditStandardResultId, modelDto);
                    CompletableFuture<Void> checkStd = CompletableFuture.supplyAsync(() -> {
                        long s1 = System.currentTimeMillis();
                        boolean isPass = reportStandardCheck.generate(toModelDtoStd);
                        long s2 = System.currentTimeMillis();
                        log.info("模型[{}]的标准检查完成，是否通过情况：{} cost:{}s", modelName, isPass, (s2 - s1) / 1000);
                        return isPass;
                    }).thenAccept(checkStdIsPass::add);
                    CompletableFuture<Void> future = CompletableFuture.allOf(checkStd, checkChi);
                    try {
                        future.get();
                        log.info("执行模型[{}]，稽核报告完成", modelName);
                    } catch (InterruptedException | ExecutionException e) {
                        log.error("等待异步报告生成失败", e);
                        setSysAuditStatus(systemId, AuditStatus.AUDIT_WAIT, false);
                        throw new DapException("等待异步报告生成失败," + e.getMessage());
                    }
                });
        log.info("获取到中文检查异步处理结果为：{}", checkChiIsPass);
        log.info("获取到标准检查异步处理结果为：{}", checkStdIsPass);
        boolean isPass1 = checkChiIsPass.stream().allMatch(Boolean.TRUE::equals);
        boolean isPass2 = checkStdIsPass.stream().allMatch(Boolean.TRUE::equals);
        auditSystemResultService.updateCheckResultById(sysAuditChineseResultId, isPass1);
        auditSystemResultService.updateCheckResultById(sysAuditStandardResultId, isPass2);
        // 更新系统信息对应的稽核情况
        sysInfo.setAuditStatus(AuditStatus.AUDIT_DONE);
        // 已完成切换到最新的稽核报告
        sysInfo.setAuditResultId(sysAuditMainResultId);
//        sysInfo.updateById();
        this.setSysAuditStatus(systemId, sysInfo.getAuditResultId(), sysInfo.getAuditStatus(), true);
        log.info("系统稽核完成，系统{}切换到新的稽核报告关联id{}，cost:{}s", systemId, sysAuditMainResultId, (System.currentTimeMillis() - start) / 1000);
//        系统稽核完成，系统1293118031607209985切换到新的稽核报告关联id1310392036863635458，cost:12s
    }

    /**
     * 增加修改稽核状态接口
     *
     * @param systemId
     * @param auditStatus
     * @param auditTime
     */
    @Override
    public void setSysAuditStatus(String systemId, AuditStatus auditStatus, boolean auditTime) {
        // auditResultId =null 保留系统上一次稽核报告id
        setSysAuditStatus(systemId, null, auditStatus, auditTime);
    }

    @Override
    public void setSysAuditStatus(String systemId, String auditResultId, AuditStatus auditStatus, boolean updateAuditTime) {
        // 启动流程状态
        ResultBean<Boolean> dapSystemInfo = systemInfoService.updateAuditStatusById(auditStatus.getCode(), auditResultId, updateAuditTime, systemId);
        BmAssetUtils.isTrue(dapSystemInfo.getContent(), () -> new DapException("更新系统稽核状态失败"));
    }

    /**
     * 基于for 提供的变量复制（深度）一份新数据
     *
     * @param sysAuditChineseResultId 对应检查报告id
     * @param modelDto                源数据dto
     * @return 新
     */
    private ModelDto copyModelDto(String sysAuditChineseResultId, ModelDto modelDto) {
        final ModelDto toModelDto = new ModelDto();
        BeanUtils.copyProperties(modelDto, toModelDto);
        toModelDto.setSysAuditCheckResultId(sysAuditChineseResultId);
        return toModelDto;
    }

    /**
     * 根据模型id获取模型的表+字段
     *
     * @param modelId 模型id
     * @return {@link ModelDto}
     */
    public ModelDto findModelInfosByModelId(String modelId) {
        ResultBean<DapDataModel> modelResultBean = dataModelService.getById(modelId);
        BmAssetUtils.isFalse(modelResultBean.getContent() == null, "获取模型信息失败");
        DapSystemDatasource dataSource = datasourceService.getById(modelResultBean.getContent().getDataSourceId());
        List<ModelTableDto> tables = findTableInfosByModelId(modelId, true);
        ModelDto dataModelDto = new ModelDto();
        dataModelDto.setTableModelDtos(tables);
        dataModelDto.setModel(modelResultBean.getContent());
        dataModelDto.setDatabaseType(dataSource.getDatabaseType());
        return dataModelDto;
    }

    /**
     * 根据模型获取表信息和字段（基于sql)
     *
     * @param modelId
     * @return
     */
    private List<ModelTableDto> findTableInfosByModelId(String modelId) {
        List<DapDataModelTableDto> tablePublishInfos = modelTableService.getTablePublishInfos(modelId, 0);
        return tablePublishInfos.stream().map(ModelTableDto::new).collect(toList());
    }

    /**
     * 根据模型获取表和字段信息
     *
     * @param modelId
     * @param sqlSelect
     * @return
     */
    @Override
    public List<ModelTableDto> findTableInfosByModelId(String modelId, boolean sqlSelect) {
        return sqlSelect ? findTableInfosByModelId(modelId) : findTableInfosByModelIdInJava(modelId);
    }

    /**
     * 根据模型获取表信息和字段（基于java)
     *
     * @param modelId
     * @return
     */
    private List<ModelTableDto> findTableInfosByModelIdInJava(String modelId) {
       throw new UnsupportedOperationException("不支持使用java获取");
    }

    /**
     * 根据系统获取模型信息只有模型信息
     *
     * @param systemId 系统
     * @return 根据系统获取模型信息
     */
    @Override
    public List<DapDataModel> findModelsBySystemId(String systemId) {
        return dataModelService.findBySystemId(systemId);
    }

    /**
     * 根据系统获取模型信息
     *
     * @param systemId 系统
     * @return 根据系统获取模型信息
     */
    @Override
    public List<ModelDto> findModelAndTableAndFieldsBySystemId(String systemId) {
        return findModelsBySystemId(systemId).stream()
                .map(d -> findModelInfosByModelId(d.getId()))
                .collect(toList());
    }

    /**
     * 获取中文检查（表级）报告
     *
     * @param searchV
     * @return
     */
    @Override
    public IPage<DapAuditStandardResult> findAuditSystemAuditStdResults(SysAuditReportSearchVo<DapAuditStandardResult> searchV) {
        //noinspection unchecked
        LambdaQueryWrapper<DapAuditStandardResult> eq = auditStandardResultService.bmLambdaQuery()
                .and(StringUtils.isNotBlank(searchV.getKeyword()), (wrapper) -> {
                    return wrapper
                            .like(StringUtils.isNotBlank(searchV.getKeyword()), DapAuditStandardResult::getTableName, searchV.getKeyword())
                            .or()
                            .like(StringUtils.isNotBlank(searchV.getKeyword()), DapAuditStandardResult::getTableChinName, searchV.getKeyword());
                })
                .eq(DapAuditStandardResult::getSystemAuditResultId, searchV.getSysAuditId())
                .eq(StringUtils.isNotBlank(searchV.getModelId()), DapAuditStandardResult::getModelId, searchV.getModelId())
                .orderByAsc(DapAuditStandardResult::getIsPassed, DapAuditStandardResult::getModelName, DapAuditStandardResult::getTableName);
        return auditStandardResultService.page(searchV.toMpPage(), eq);
    }

    /**
     * 获取中文检查（字段级）报告
     *
     * @param searchV
     * @return
     */
    @Override
    public IPage<DapAuditFieldStandardResult> findAuditFieldStdResults(SysAuditReportFieldSearchVo<DapAuditFieldStandardResult> searchV) {
        LambdaQueryWrapper<DapAuditFieldStandardResult> eq = auditFieldStandardResultService.bmLambdaQuery()
                .and(StringUtils.isNotBlank(searchV.getKeyword()), (wrapper) -> {
                    return wrapper
                            .like(StringUtils.isNotBlank(searchV.getKeyword()), DapAuditFieldStandardResult::getFieldName, searchV.getKeyword())
                            .or()
                            .like(StringUtils.isNotBlank(searchV.getKeyword()), DapAuditFieldStandardResult::getFieldChinName, searchV.getKeyword());
                })
                .eq(DapAuditFieldStandardResult::getStandardResultId, searchV.getAuditTableResultId())
                .orderByAsc(DapAuditFieldStandardResult::getIsPassed);
        return auditFieldStandardResultService.page(searchV.toMpPage(), eq);
    }

    /**
     * // sheet1 DapAuditChinResult
     * // sheet2 DapAuditFieldChinResult
     *
     * @param searchV 为空获取所有报告
     * @return
     */
    @Override
    public Workbook exportAuditChinResults(SysAuditReportSearchVo<DapAuditChinResult> searchV, String key) {
        List<DapAuditChinResult> chinAndFieldResult = auditChinResultService.getBaseMapper(DapAuditChinResultMapper.class).getChinAndFieldResult(searchV);
        try {
            if (EXPORT_CHI_RESULT_KEY.equals(key)) {
                return exportChinSheet1(chinAndFieldResult);
            } else if (EXPORT_CHI_FIELD_RESULT_KEY.equals(key)) {
                return exportChinSheet2(chinAndFieldResult);
            }
        } catch (IOException e) {
            log.error("导出中文检查异常", e);
        }
        return null;
    }

    /**
     * @param chinAndFieldResult 结果数据u
     * @return 生成
     * @throws IOException
     */
    private Workbook exportChinSheet1(List<DapAuditChinResult> chinAndFieldResult) throws IOException {
        ExportParams params = new ExportParams("系统稽核-中文检查", "表中文名检查", ExcelType.XSSF);
        Workbook workbook = ExcelExportUtil.exportExcel(params, DapAuditChinResult.class, chinAndFieldResult);
        return workbook;
    }

    /**
     * @param chinAndFieldResult 结果数据u
     * @return 生成
     * @throws IOException
     */
    private Workbook exportChinSheet2(List<DapAuditChinResult> chinAndFieldResult) throws IOException {
        if (chinAndFieldResult == null) {
            chinAndFieldResult = new ArrayList<>();
        }
        List<DapAuditFieldChinResult> fieldChinResults = chinAndFieldResult.stream().filter(d -> d.getIsPassed() != 1).flatMap(d ->
                d.getFieldResult().stream().filter(f -> f.getIsPassed() != 1).peek(f -> {
                    f.setModelName(d.getModelName());
                    f.setTableName(d.getTableName());
                    f.setTableChinName(d.getTableChinName());
                }).collect(toList()).stream()).collect(toList());
        ExportParams params = new ExportParams("系统稽核-中文检查", "字段中文名缺少列表", ExcelType.XSSF);
        Workbook workbook = ExcelExportUtil.exportExcel(params, DapAuditFieldChinResult.class, fieldChinResults);
        return workbook;
    }

    /**
     * 导出
     *
     * @param searchVo 查询条件
     */
    @Override
    public Workbook exportAuditStdResults(SysAuditReportSearchVo<DapAuditStandardResult> searchV, String key) {
        List<DapAuditStandardResult> stdAndField = auditStandardResultService.getBaseMapper(DapAuditStandardResultMapper.class).getStdAndField(searchV);
        List<DapAuditFieldStandardResult> fieldStdResult = stdAndField.stream().flatMap(d ->
                d.getFieldResult().stream().peek(f -> {
                    f.setModelName(d.getModelName());
                    f.setTableName(d.getTableName());
                    f.setTableChinName(d.getTableChinName());
                }).collect(toList()).stream())
                .collect(toList());
        if (EXPORT_STD_RESULT_KEY.equals(key)) {
            ExportParams params = new ExportParams("系统稽核-标准检查", "标准稽核列表", ExcelType.XSSF);
            Workbook workbook = ExcelExportUtil.exportExcel(params, DapAuditFieldStandardResult.class, fieldStdResult);
            return workbook;
        } else if (EXPORT_STD_FIELD_RESULT_KEY.equals(key)) {
            List<AuditFieldStdNoRelationResult> unRel = fieldStdResult.stream().filter(f -> f.getIsPassed() != 1)
                    .map(AuditFieldStdNoRelationResult::new).collect(toList());
            ExportParams params = new ExportParams("系统稽核-标准检查", "未关联标准列表", ExcelType.XSSF);
            Workbook workbook = ExcelExportUtil.exportExcel(params, AuditFieldStdNoRelationResult.class, unRel);
            return workbook;
        }
        return null;
    }


}
